推广 TDD 的一点思考

读到同事张逸的一篇博文:推行 TDD 的思考,我也有感而发。 我刚加入 ThoughtWorks 时,工作在一个合作开发的 Android 项目上,我们除了要 TDD 外,也要求并指导客户公司的开发人员进行 TDD,项目开发了快半年,测试覆盖率稳定在 90%以上。 业余时间,我主持过 GDCR 和 Coding Dojo 等活动。不管在企业还是社区,我见到很多开发者对 TDD 的理解还停留在字面上。 我认为推行 TDD 主要在两个方面努力,即意识和能力(好像任何事情都是这两方面哈)。

意识方面主要是:

  1. 认识到自己原有编程方法的不足;
  2. 搞清楚 TDD 的价值所在,如何弥补原有方法的不足;
  3. 心态开放,勇于尝试新鲜事物,不要浅尝辄止,要持续改进。

意识和能力没有先后关系,而是在不断学习和实践过程中同时提高。在这篇文章中,我更想谈的是能力。 我见到初接触 TDD 的人常犯下面的错误:

  1. 在声明测试方法后,便开始写实现代码;
  2. 写完“所有”的测试代码才开始写实现;
  3. 一次实现过多的代码(超出当前测试覆盖的业务);
  4. 从不重构;

TDD 真是看起来容易,做起来难。

TDD Cycle

上面这个图一目了然,但其实每一步都是对能力有要求的:

测试先行并不是说不需要思考,直接开始写代码。在开始写代码之前要进行需求分析,将需求分解为任务列表,再从列表中挑选一个任务,转换成一组测试用例,然后不断循环去实现。测试代码其实是产品代码的“用户”,在写测试代码时你就要考虑如何“使用”产品代码,是一个实例方法还是一个类方法,是从构造函数传参还是从方法调用传参,方法的命名,返回值等。这时其实我们就是在做设计,而且设计以代码来体现,比在脑袋中空想要更直观。很多人不懂“意图式编程”,总是习惯先实现一个东西,再去调用它。而测试先行就要求先使用,再实现。这样能少走很多弯路,减少返工。

绿

以最快的速度让测试变绿,意味着我们通常用最直接但可能并不优雅的方式,比如复制代码。然后小步重构,直到符合简单设计的原则:

  1. 通过所有测试
  2. 每个概念都被清楚地表达
  3. 没有重复
  4. 没有多余的东西

难的是要让实现刚好满足当前的测试,不做过度的设计,不写多余的代码。因为如果你写多了,除了引入复杂性以外,多的那部分就没有测试能覆盖到。或者你后面的测试写出来就能直接变绿,你就没办法按 TDD 的节奏进行下去了。

重构

首先要能识别坏味道,一些低级的 Smell,很容易识别,比如:Magic Number,重复代码,太大的类,太长的方法,命名等。但更高级的如Feature EnvyLazy Class等就比较难以识别。

只要识别到 Smell,知道用什么手法去重构,剩下的就比较简单了,现代的 IDE,尤其是JetBrains的产品,对重构的支持非常强大,几乎都可以用快捷键完成。选对工具非常重要,善假于物能极大提高开发效率。但也不能过分依赖工具,要明白每一个手法背后的原理。所以推荐每个想要实践 TDD 的开发者,一定要先读《重构》。

最后我想说: TDD 不是银弹,不可能适合所有的场景,但这不应该成为我们拒绝它的理由。 也不要轻易否定 TDD,如果要否定,起码要在认真实践过之后。